package behaviorlearning;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import planninglanguage.goals.Goal;
import planninglanguage.goals.WinWargusGoal;
import util.XMLWriter;
import base.WargusMap;
import base.WargusPlayer;
import base.WargusUnit;


public class WargusTraceLoader {
		
	static List<Case> load_Wargus_trace(BufferedReader in, BufferedReader in_goal) throws Exception {
		WargusMap initial_map = read_Wargus_map(in);
		String token;
		ArrayList< List<String> > goal_annotations = new ArrayList< List<String> >();
		List<Case> cases = new LinkedList<Case>();
		
		if (in_goal!=null) read_goal_annotations(in_goal,goal_annotations);

		token = getToken(in); 
		while(token!=null) {
			if (!token.equals("Log")) throw new Exception("The first token in an event should be a 'Log'! found '" + token + "'");
			
			getToken(in);	// (

			process_trace_event(in,initial_map,goal_annotations,cases);
			
			getToken(in);	// )
			
			token = getToken(in);
		}		
		
		return cases;

	}

	static void read_goal_annotations(BufferedReader in,ArrayList<List<String>> ga) throws Exception {
		String token;
		String goal;
		
		token = getToken(in);
		while(token!=null) {
			
			goal = token;
			
			getToken(in); // {
			token = getToken(in);
			while( !token.equals("}")) {
				int pos = Integer.parseInt(token);				
				List<String> l;
				
				while(ga.size()<=pos) ga.add(null); 
				l = ga.get(pos);
				
				if (l==null) {
					l = new LinkedList<String>();
					ga.set(pos, l);
				}
				
				l.add(goal);
				
				// System.out.println("State " + pos + " Goal: " + goal);
				
				token = getToken(in);
			}
			
			
			token = getToken(in);
		}
	}
	
	static WargusMap read_Wargus_map(BufferedReader in) throws IOException {
		int dx = 16,dy = 16,i;
		WargusMap map;
		String token;
		
		// InitialMap( { width = 32, height = 32,
		token = getToken(in); // InitialMap 
		token = getToken(in); // (
		token = getToken(in); // { 
		token = getToken(in); // width 
		token = getToken(in); // = 
		token = getToken(in); // [width]
		dx = Integer.parseInt(token);
		token = getToken(in); // , 
		token = getToken(in); // height 
		token = getToken(in); // =
		token = getToken(in); // [height]
		dy = Integer.parseInt(token);
		token = getToken(in); // , 

		map = new WargusMap(dx,dy);

		token = getToken(in); // { 
		for(i=0;i<dx*dy;i++) {
			token = getToken(in);
			while(!token.equals(",")) {
//				if (token.equals("grass")) map.set_map(i%dx, i/dx, '.');
				if (token.equals("water")) map.set_map(i%dx, i/dx, 'w');
				if (token.equals("wall")) map.set_map(i%dx, i/dx, 'X');
				if (token.equals("tree")) map.set_map(i%dx, i/dx, 't');
				token = getToken(in);
			}
		} // for

		token = getToken(in); // } 
		token = getToken(in); // } 
		token = getToken(in); // ) 
		
		return map;
	}
	
	static void process_trace_event(BufferedReader in,WargusMap map,ArrayList<List<String>> ga,List<Case> cases) throws Exception
	{
//		int cycle = 0;
		String token;

		token = getToken(in); // { 
		getToken(in); // GameCycle
		getToken(in); // = 
		token = getToken(in); // [cycle]
//		cycle = Integer.parseInt(token);
		getToken(in); // , 		
		
//		System.out.println("Cycle: " + cycle);

		token = getToken(in); // Player | UnitSummery | Player
				
		if (token.equals("WoodChopped")) {
			// WoodChopped event:			
			int x,y;
			
			/*
			Log( { GameCycle = 3270, WoodChopped, PosX = 13 , PosY = 16 } )		 
			 */
			
			getToken(in); // , 
			getToken(in); // PosX 
			getToken(in); // = 
			token = getToken(in); // [PosX]
			x = Integer.parseInt(token);
			getToken(in); // , 
			getToken(in); // PosY 
			getToken(in); // = 
			token = getToken(in); // [PosY]
			y = Integer.parseInt(token);
			map.set_map(x, y, '.');
			getToken(in); // } 
			return;
		} else if (token.equals("NewUnit")) {
			int ID = 0, x = 0, y = 0, creator = 0;
			
			/* 
			Log( { GameCycle = 11, NewUnit, ID = 7, PosX = 28 , PosY = 13, Type = "unit-pig-farm", Creator = 1 } )
			 */

			getToken(in); // , 
			getToken(in); // ID 
			getToken(in); // = 
			token = getToken(in);
			ID = Integer.parseInt(token);
			getToken(in); // , 
			getToken(in); // PosX 
			getToken(in); // = 
			token = getToken(in);
			x = Integer.parseInt(token);
			getToken(in); // , 
			getToken(in); // PosY 
			getToken(in); // = 
			token = getToken(in);
			y = Integer.parseInt(token);
			getToken(in); // , 
			getToken(in); // Type 
			getToken(in); // = 
			getToken(in); // [unit type] 
			getToken(in); // , 
			getToken(in); // Creator 
			getToken(in); // = 
			token = getToken(in);
			creator = Integer.parseInt(token);
			getToken(in); // } 
			
			{
				Case creation_action = null;
				
				for(Case c:cases) {
					if (creation_action==null && 
						c.m_solution.m_action.m_unitID == creator &&
						c.m_solution.m_action.getConstructedUnitID() == -1 &&
						(c.m_solution.m_action.m_action.equals("train") ||
						 c.m_solution.m_action.m_action.equals("build"))) creation_action = c;
				}
				
				if (creation_action!=null) {
					creation_action.m_solution.m_action.setConstructedUnitID(ID);
				} else {
					System.out.println("No matching construction command for constructing unit " + ID + " assuming it's an enemy unit...");				
				}
			}
			

		} else {
			
			WargusAction w_action = null;
			
			List<WargusUnit> units = new LinkedList<WargusUnit>();
			List<WargusPlayer> players = new LinkedList<WargusPlayer>();

			/*
			Log( { GameCycle = 8, Player = 1, UnitNumber = 2, UnitIdent = "unit-peon", Action = "build", Flush = 1, x = 26, y = 20, Value = "unit-pig-farm", SyncRandSeed = 2271560481 } )
			*/

			// Read the action:
			{
				int player=-1,unitID=-1,destUnitID = -1;
				String unitType=null,action=null;
				int x=-1,y=-1;
				String value=null;

				getToken(in); // = 
				token = getToken(in); // [player]
				player = Integer.parseInt(token);
				
				token = getToken(in);
				while(token.equals(",")) {
					
					String fname = getToken(in); // UnitNumber 
					getToken(in); // = 
					token = getToken(in); // [unitID]
					
					if (fname.equals("UnitNumber")) unitID = Integer.parseInt(token);
					if (fname.equals("UnitIdent")) unitType = token;
					if (fname.equals("Action")) action = token;
					if (fname.equals("x")) x = Integer.parseInt(token);
					if (fname.equals("y")) y = Integer.parseInt(token);
					if (fname.equals("Value")) value = token;
					if (fname.equals("DestUnitNumber")) destUnitID = (Integer.parseInt(token))+1;
					token = getToken(in);
					
				}
				
				w_action = new WargusAction(player,unitID,unitType,action,x,y,value,destUnitID,players);
								
				getToken(in); // )
			}
			
			/*
			Log( { GameCycle = 8,  UnitSummary , NUnits = 6 
				{ 
					 ( UnitID = 1 , Player = 15 , Type = "unit-gold-mine" , x = 0 , y = 21 , current-hitpoints = 25500 ) 
					 ( UnitID = 2 , Player = 1 , Type = "unit-peon" , x = 27 , y = 20 , current-hitpoints = 30 ) 
					 ( UnitID = 3 , Player = 0 , Type = "unit-town-hall" , x = 6 , y = 14 , current-hitpoints = 1200 ) 
					 ( UnitID = 4 , Player = 1 , Type = "unit-great-hall" , x = 23 , y = 14 , current-hitpoints = 1200 ) 
					 ( UnitID = 5 , Player = 0 , Type = "unit-peasant" , x = 4 , y = 20 , current-hitpoints = 30 ) 
					 ( UnitID = 6 , Player = 15 , Type = "unit-gold-mine" , x = 29 , y = 21 , current-hitpoints = 25500 )  
				} 
					 ,
					 Resources {  ( Player = 0 , gold = 2000 , wood = 1500 , oil = 1000 )  
					 			  ( Player = 1 , gold = 2000 , wood = 1500 , oil = 1000 )  }  
				} )			 
			 */
			
			// Read the game state:
			{
				int i;
				int nu;
				
				getToken(in); // Log
				getToken(in); // (
				getToken(in); // {
				getToken(in); // GameCycle
				getToken(in); // =
				getToken(in); // [GameCycle]
				getToken(in); // ,
				getToken(in); // UnitSummary
				getToken(in); // ,
				getToken(in); // NUnits
				getToken(in); // =
				token = getToken(in); // [NUnits]
				nu = Integer.parseInt(token);
				getToken(in); // {				
				for(i=0;i<nu;i++) {
					int unit_ID,unit_x,unit_y,unit_chp,unit_cmp = -1,unit_playerID;
					String unit_type;

					getToken(in); // (
					getToken(in); // UnitID
					getToken(in); // =
					token = getToken(in); // [UnitID]
					unit_ID = Integer.parseInt(token);
					getToken(in); // ,
					getToken(in); // Player
					getToken(in); // =
					token = getToken(in); // [Player]
					unit_playerID = Integer.parseInt(token);
					getToken(in); // ,
					getToken(in); // Type
					getToken(in); // =
					unit_type = getToken(in); // [UnitType]
					getToken(in); // ,
					getToken(in); // x
					getToken(in); // =
					token = getToken(in); // [x]
					unit_x = Integer.parseInt(token);
					getToken(in); // ,
					getToken(in); // y
					getToken(in); // =
					token = getToken(in); // [y]
					unit_y = Integer.parseInt(token);
					getToken(in); // ,
					getToken(in); // chp
					getToken(in); // =
					token = getToken(in); // [chp]
					unit_chp = Integer.parseInt(token);
					
					getToken(in); // )

					{
						WargusUnit u = new WargusUnit(unit_ID,unit_playerID,unit_type,unit_x,unit_y,unit_chp,unit_cmp);
						
						// If the unit is not a dead body or destroyed building:
						if (u.getType()!=105 &&
							u.getType()!=106 &&
							u.getType()!=107 &&
							u.getType()!=108 &&
							u.getType()!=109) units.add(u);		
						}
				}
				getToken(in); // }
				
				getToken(in); // ,
				getToken(in); // Resources
				getToken(in); // {
				
				token = getToken(in);
				while(token.equals("(")) {
					int playerID;
					int player_gold,player_wood,player_oil;
					
					// Player = 0 , gold = 2000 , wood = 1500 , oil = 1000 )
					
					getToken(in); // Player
					getToken(in); // =
					token = getToken(in); // [PlayerID]
					playerID = Integer.parseInt(token);
					// System.out.println("Player: " + playerID);
					getToken(in); // ,
					getToken(in); // gold
					getToken(in); // =
					token = getToken(in); // [gold]
					player_gold = Integer.parseInt(token);
					getToken(in); // ,
					getToken(in); // wood
					getToken(in); // =
					token = getToken(in); // [wood]
					player_wood = Integer.parseInt(token);
					getToken(in); // ,
					getToken(in); // oil
					getToken(in); // =
					token = getToken(in); // [oil]				
					player_oil = Integer.parseInt(token);
					
					// Create a new player:
					{
						Vector<WargusUnit> l = new Vector<WargusUnit>();
						
						for(WargusUnit u:units) {
							if (u.getPlayerID()==playerID) l.add(u);
						} // for

						WargusPlayer p = new WargusPlayer(playerID,player_gold,player_wood,player_oil, l);
											
						players.add(p);						
					}
					token = getToken(in); // )
					token = getToken(in); // (
				}			
				getToken(in); // }
				
				// Save an action:
				if (w_action.m_playerID == 0) {
					List<String> goals = new LinkedList<String>();
					map.get_goldMines().clear();
					for(WargusUnit u:units) {
						if (u.getPlayerID()==15) {
							if (u.getType()==92) map.get_goldMines().add(u);
							else map.get_oilPatches().add(u);
						}
					}

					LinkedList<Integer> opponents = new LinkedList<Integer>();
					opponents.add(1); // XXX hack hard-coding player & enemy ID's
					Goal winGoal = new WinWargusGoal(0, opponents);
					
					StringWriter goalStringer = new StringWriter();
					XMLWriter w = new XMLWriter(new PrintWriter(goalStringer));
					w.setTab(2);
					winGoal.writeXML(w);
					
					goals.add(goalStringer.toString());
					
					if (ga.size()>cases.size()) {
						List<String> case_goals = ga.get(cases.size());
						if (case_goals != null) {
							for(String g:case_goals) { 
								goals.add(g);
							} // for
						} else {
							System.out.println("No Goal annotations for action: " + cases.size() + " " + w_action.toString());
						} // if 
					} else {
						System.out.println("No Goal annotations for action: " + cases.size() + " " + w_action.toString());	
					}
				
					w_action.update_units(players, map);
						
						
					if (w_action.m_u!=null) {
						if (w_action.m_u!=null && w_action.m_action != "return") { 
							// XXX we don't handle returns and they only seem to be used by enemy peons
							Case c = new Case(cases.size(),goals,map,players,w_action);
							cases.add(c);
						} // if		
					} else {
						System.out.println("Action has no unit specified:" + w_action.toString());						
					}
				}
			}
		
		} // if 
	}
	
		
	static String TokenLineBuffer = null; 
	static int TokenPosBuffer = 0;
	static String getToken(BufferedReader in) throws IOException {
		String token = "";
		char c;
		boolean end=false;
		int state;
		state = 0;

		while(!end) {
			if (TokenLineBuffer == null || TokenPosBuffer >= TokenLineBuffer.length()) {
				TokenLineBuffer = in.readLine();
				while(TokenLineBuffer != null && TokenLineBuffer.length() == 0) {
					TokenLineBuffer = in.readLine();
				}
				TokenPosBuffer = 0;
			}
			if (TokenLineBuffer == null) return null;
			c = TokenLineBuffer.charAt(TokenPosBuffer);
			TokenPosBuffer++;

			if (c!=0) {
				switch(state) {
				case 0:
					if (c==',' || c=='(' || c==')' || c=='{' || c=='}') {
						token = token + c;
						end=true;
					} else if (c=='\"') {
						state=3;
					} else if ((c>='0' && c<='9') || c=='-') {
						token = token + c;
						state=2;
					} else if (c!=' ' && c!='\r' && c!='\n' && c!='\t') {
						token = token + c;
						state=1;
					} /* if */ 
					break;
				case 1:
					if (c!=',' && c!=' ' && c!='\r' && c!='\n' && c!='\t' && 
						c!='(' && c!=')' && c!='{' && c!='}' && c!='\"') {
						token = token + c;
					} else {
						TokenPosBuffer--;
						end=true;
					} /* if */ 
					break;
				case 2:
					if ((c>='0' && c<='9') || c=='.') {
						token = token + c;
					} else {
						TokenPosBuffer--;
						end=true;
					} /* if */ 
					break;
				case 3:
					if (c == '\\') {
						c = TokenLineBuffer.charAt(TokenPosBuffer);
						TokenPosBuffer++;
						token += c;
					} else if (c!='"') {
						token = token + c;
					} else {
						end=true;
					} /* if */ 
					break;
				case 4:
					if (c!=' ' && c!='\r' && c!='\n' && c!='\t' && 
						c!='(' && c!=')' && c!='\"') {
						token = token + c;
					} else {
						TokenPosBuffer--;
						end=true;
					} /* if */ 
					break;
				} /* switch */ 
			} else {
				end=true;
			} /* if */ 
		} /* while */ 

		return token;
	} /* FeatureTerm::getTokenNOOS */ 

}
